import abc
import ctypes
import enum
from typing import Optional, Union


class ShutterTTLSignal(enum.Enum):
    LOW = 0
    HIGH = 1


class ShutterMode(enum.Enum):
    FULLY_AUTO = 0
    PERMANENTLY_OPEN = 1
    PERMANENTLY_CLOSED = 2
    OPEN_FOR_FVB_SERIES = 4
    OPEN_FOR_ANY_SERIES = 5


class EMGainMode(enum.Enum):
    DAC255 = 0
    DAC4095 = 1
    LINEAR = 2
    REAL = 3


class ReadMode(enum.Enum):
    FULL_VERTICAL_BINNING = 0
    MULTI_TRACK = 1
    RANDOM_TRACK = 2
    SINGLE_TRACK = 3
    IMAGE = 4


class AcquisitionMode(enum.Enum):
    SINGLE_SCAN = 1
    ACCUMULATE = 2
    KINETICS = 3
    FAST_KINETICS = 4
    RUN_TILL_ABORT = 5


class Status(enum.Enum):
    IDLE = 20073
    TEMPCYCLE = 20074
    ACQUIRING = 20072
    ACCUM_TIME_NOT_MET = 20023
    KINETIC_TIME_NOT_MET = 20022
    ERROR_ACK = 20013
    ACQ_BUFFER = 20018
    ACQ_DOWNFIFO_FULL = 20019
    SPOOLERROR = 20026


class EmccdCamera(abc.ABC):
    @staticmethod
    def from_path(dll_path: Optional[str]) -> 'EmccdCamera':
        if dll_path == "<fake>":
            from cion.emccd.camera.camera_fake import EmccdCameraFake
            return EmccdCameraFake()
        else:
            from cion.emccd.camera.camera_dll import EmccdCameraByDll
            return EmccdCameraByDll.from_path(dll_path)

    # lifecycle

    def __init__(self):
        self._initialize()

    def __del__(self):
        self._shutdown()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._shutdown()

    def shutdown(self):
        self._shutdown()

    @abc.abstractmethod
    def _initialize(self):
        pass

    @abc.abstractmethod
    def _shutdown(self):
        pass

    # information

    @abc.abstractmethod
    def get_head_model(self) -> str:
        pass

    @abc.abstractmethod
    def get_detector_size(self) -> tuple[int, int]:
        pass

    # temperature

    @abc.abstractmethod
    def get_temperature_range(self) -> tuple[int, int]:
        pass

    @abc.abstractmethod
    def set_temperature(self, temperature: int):
        pass

    @abc.abstractmethod
    def get_temperature(self) -> int:
        pass

    @abc.abstractmethod
    def set_cooler(self, on: bool):
        pass

    # shutter

    @abc.abstractmethod
    def set_shutter(self, signal: ShutterTTLSignal, mode: ShutterMode, closing_time: int, opening_time: int):
        pass

    # pre-amp (pre-amplifier)

    @abc.abstractmethod
    def get_pre_amp_gains_num(self) -> int:
        pass

    @abc.abstractmethod
    def get_pre_amp_gain(self, index: int) -> float:
        pass

    @abc.abstractmethod
    def set_pre_amp_gain(self, index: int):
        pass

    # em (electron multiplying)

    @abc.abstractmethod
    def set_em_advanced(self, on: bool):
        pass

    @abc.abstractmethod
    def get_em_advanced(self) -> bool:
        pass

    @abc.abstractmethod
    def set_em_gain_mode(self, mode: EMGainMode):
        pass

    @abc.abstractmethod
    def get_em_gain_range(self) -> tuple[int, int]:
        pass

    @abc.abstractmethod
    def set_em_gain(self, em_gain: int):
        pass

    @abc.abstractmethod
    def get_em_gain(self) -> int:
        pass

    # baseline

    @property
    @abc.abstractmethod
    def support_baseline_clamp(self) -> bool:
        pass

    @abc.abstractmethod
    def set_baseline_clamp(self, enable: bool):
        pass

    @property
    @abc.abstractmethod
    def support_baseline_offset(self) -> bool:
        pass

    @abc.abstractmethod
    def set_baseline_offset(self, offset: int):
        pass

    # vertical shift speeds

    @abc.abstractmethod
    def get_vs_speeds_num(self) -> int:
        pass

    @abc.abstractmethod
    def get_vs_speed(self, vs_speed_index: int) -> float:
        """  vertical shift speed (ms/pixel) """
        pass

    @abc.abstractmethod
    def set_vs_speed(self, vs_speed_index: int):
        pass

    @abc.abstractmethod
    def get_fastest_recommended_vs_speed(self) -> tuple[int, float]:
        """ vs_speed_index, speed (ms/pixel) """
        pass

    def set_as_fastest_recommended_vs_speed(self):
        vs_speed_index, _ = self.get_fastest_recommended_vs_speed()
        self.set_vs_speed(vs_speed_index)

    # ad channels & horizontal shift speeds

    @abc.abstractmethod
    def get_ad_channels_num(self) -> int:
        pass

    @abc.abstractmethod
    def get_hs_speeds_num(self, ad_channel_index: int, amplification: int) -> int:
        pass

    @abc.abstractmethod
    def get_hs_speed(self, ad_channel_index: int, amplification: int, hs_speed_index: int) -> float:
        """ Horizontal shift speed (MHz) """
        pass

    @abc.abstractmethod
    def set_ad_channel(self, ad_channel_index: int):
        pass

    @abc.abstractmethod
    def set_hs_speed(self, amplification: int, hs_speed_index: int):
        pass

    def get_fastest_ad_channel_hs_speed(self, amplification: int) -> tuple[int, int, float]:
        best_ad_channel_index, best_hs_speed_index, best_hs_speed = 0, 0, 0.0

        ad_channel_num = self.get_ad_channels_num()
        for ad_channel_index in range(ad_channel_num):
            hs_speed_num = self.get_hs_speeds_num(ad_channel_index, amplification)
            for hs_speed_index in range(hs_speed_num):
                hs_speed = self.get_hs_speed(ad_channel_index, amplification, hs_speed_index)
                if hs_speed > best_hs_speed:
                    best_ad_channel_index = ad_channel_index
                    best_hs_speed_index = hs_speed_index
                    best_hs_speed = hs_speed

        return best_ad_channel_index, best_hs_speed_index, best_hs_speed

    def set_as_fastest_ad_channel_hs_speed(self, amplification: int = 0):
        ad_channel_index, hs_speed_index, hs_speed = self.get_fastest_ad_channel_hs_speed(amplification)
        self.set_ad_channel(ad_channel_index)
        self.set_hs_speed(amplification, hs_speed_index)

    # read mode & set image

    @abc.abstractmethod
    def set_read_mode(self, mode: ReadMode):
        pass

    @abc.abstractmethod
    def set_image(self, *, hbin: int, hstart: int, hend: int, vbin: int, vstart: int, vend: int):
        # sdk 里的 start 和 end 都是 inclusive 的，并且从 1 开始计
        # 为了迎合编程习惯，此处改为从 0 开始计，并且 end 是 exclusive 的
        pass

    # acquisition mode & exposure & cycle

    @abc.abstractmethod
    def set_acquisition_mode(self, mode: AcquisitionMode):
        pass

    @abc.abstractmethod
    def set_exposure_time(self, time: float):
        pass

    @abc.abstractmethod
    def set_kinetic_cycle_time(self, time: float):
        pass

    @abc.abstractmethod
    def get_acquisition_times(self) -> tuple[float, float, float]:
        """ exposure_time, accumulate_cycle_time, kinetic_cycle_time """
        pass

    # acquisition control

    @abc.abstractmethod
    def start_acquisition(self):
        pass

    @abc.abstractmethod
    def abort_acquisition(self):
        pass

    @abc.abstractmethod
    def get_status(self) -> Status:
        pass

    # data

    @abc.abstractmethod
    def get_acquired_data(self, size: int, buffer: Union[ctypes.POINTER, ctypes.Array]):
        pass

    @abc.abstractmethod
    def get_latest_image(self, size: int, buffer: Union[ctypes.POINTER, ctypes.Array]):
        pass
